using System;
using System.Data;
using System.Xml;
using System.Collections;
using System.Runtime.InteropServices;
using System.ComponentModel;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Xml.Serialization;
using Microsoft.SharePoint;
using Microsoft.SharePoint.Utilities;
using Microsoft.SharePoint.WebPartPages;
using Argotic.Syndication;
using System.Collections.Generic;
using System.Net;
using System.IO;

namespace SmilingGoat.SharePoint.WebParts
{
	/// <summary>
	/// RssFeedReader is a Windows SharePoint Services Web Part providing consumption of RSS news feeds.
	/// </summary>
	[ToolboxData("<{0}:RssFeedReader runat=server></{0}:RssFeedReader>")]
	[XmlRoot(Namespace="http://schemas.smilinggoat.net/WebPart/v2/FeedReader")]
	[GuidAttribute("0DFE2B93-AD36-441F-AF24-9B1FFE2D6724")]
	public sealed class RssFeedReader : Microsoft.SharePoint.WebPartPages.WebPart, Microsoft.SharePoint.WebControls.IDesignTimeHtmlProvider
	{
		#region Private Attributes
		private string _rssFeeds;
		private const string _defaultRss = "";
		private string _proxyAddress;
		private string _proxyPort;
		private bool _headLinesOnly = true;
		private const bool _defaultHeadlines = true;
        private bool _expandHeadlines = false;
        private const bool _defaultExpandHeadlines = false;
		private const bool _defaultDates = false;
		private bool _showDates = false;
		private string _cacheDuration = "2";
		private const string _defaultCacheDuration = "2";
		private const bool _defaultUserCache = true;
		private bool _userCache = true;
		private DateTime _lastRefreshTime;
		private const string _defaultFilter = "None";
		private string _filterMode = "None";
		private string _filterVal = "15";
		private const string _defaultFilterVal = "15";
        private System.Net.IWebProxy _proxyConnection = null;
        private System.Net.ICredentials _credentials = null;
		#endregion

		#region Public Properties
		ImageButton refreshButton;

		/// <summary>
		/// The path the the included resource files for the Web Part.
		/// </summary>
		public string ResourcePath
		{
			get { return this.ClassResourcePath; }
		}

		/// <summary>
		/// DisplayFilter allows the user to choose what type of display filtering they would like.  Optional values are: Days, Items, None.
		/// </summary>
		[DefaultValue(_defaultFilter)]
		[WebPartStorage(Storage.Personal)]
		[XmlElement(ElementName="DisplayFilter", IsNullable=false)]
		public string DisplayFilter
		{
			get { return this._filterMode; }
			set { this._filterMode = value; }
		}

		/// <summary>
		/// CacheMinuteDuration provides the number of minutes the feed content should be cached for all users.
		/// </summary>
		[DefaultValue(_defaultCacheDuration)]
		[WebPartStorage(Storage.Personal)]
		[XmlElement(ElementName="CacheMinuteDuration", IsNullable=false)]
		public string CacheMinuteDuration
		{
			get { return this._cacheDuration; }
			set { this._cacheDuration = value; }
		}

		/// <summary>
		/// HideDescriptions is a boolean value telling the Web Part wether the &lt;description&gt; element should be displayed.
		/// </summary>
		[DefaultValue(_defaultHeadlines)]
		[WebPartStorage(Storage.Personal)]
		[XmlElement(ElementName="HideDescriptions", IsNullable=false)]
		public bool HideDescriptions
		{
			get { return this._headLinesOnly; }
			set { this._headLinesOnly = value; }
		}

        /// <summary>
        /// ExpandDescriptions is a boolean value telling the Web Part wether the &lt;description&gt; element should be expanded by default.
        /// </summary>
        [DefaultValue(_defaultExpandHeadlines)]
        [WebPartStorage(Storage.Personal)]
        [XmlElement(ElementName = "ExpandDescriptions", IsNullable = false)]
        public bool ExpandDescriptions
        {
            get { return this._expandHeadlines; }
            set { this._expandHeadlines = value; }
        }


		/// <summary>
		/// ShowDates instructs the Web Part to display the date of the item in the user interface.
		/// </summary>
		[DefaultValue(_defaultDates)]
		[WebPartStorage(Storage.Personal)]
		[XmlElement(ElementName="ShowDates", IsNullable=false)]
		public bool ShowDates
		{
			get { return this._showDates; }
			set { this._showDates = value; }
		}

		/// <summary>
		/// RSSFeeds is a semicolon delimited list of URIs for various RSS feeds.
		/// </summary>
		[DefaultValue(_defaultRss)]
		[WebPartStorage(Storage.Personal)]
		[XmlElement(ElementName="RssFeeds", IsNullable=false)]
		public string RssFeeds
		{
			get { return this._rssFeeds; }
			set { this._rssFeeds = value; }
		}

		[DefaultValue(_defaultFilterVal)]
		[WebPartStorage(Storage.Personal)]
		[XmlElement(ElementName="FilterValue", IsNullable=false)]
		public string FilterValue
		{
			get { return this._filterVal; }
			set { this._filterVal = value; }
		}

		[DefaultValue("")]
		[WebPartStorage(Storage.Personal)]
		[XmlElement(ElementName="ProxyAddress", IsNullable=false)]
		public string ProxyAddress
		{
			get { return this._proxyAddress; }
			set { this._proxyAddress = value; }
		}

		[DefaultValue("")]
		[WebPartStorage(Storage.Personal)]
		[XmlElement(ElementName="ProxyPort", IsNullable=false)]
		public string ProxyPort
		{
			get { return this._proxyPort; }
			set { this._proxyPort = value; }
		}
		#endregion

		#region Client Side Script Registration
		// Used for the embedded script.  Embedded javascript is using when user clicks
		// on the Up button to go up and down between folders.
		private const string EmbeddedScriptKey = "EmbeddedScriptKeyForRSSFeedReader";
		private string EmbeddedScriptFormat = "";

		//Function which will register the linked file script and the embedded script
		//By registering the script, if multiple of the same webpart place on a webpart
		//page, the script will only be download and registered once for all instance
		//of the webpart.
		protected void RegisterCommonScript() 
		{
			//Embedded Client Script
			EmbeddedScriptFormat = "<script language=\"javascript\" src=\"" + this.ClassResourcePath + "/feedreader.js" + "\"></script>";
			if(!Page.IsClientScriptBlockRegistered(EmbeddedScriptKey))
				Page.RegisterClientScriptBlock(EmbeddedScriptKey,
					EmbeddedScriptFormat);
		}
		#endregion

		#region Event Handlers
		public RssFeedReader()
		{
            _credentials = System.Net.CredentialCache.DefaultCredentials;
			this.PreRender += new EventHandler(UpdateCache);
		}

		public override ToolPart[] GetToolParts()
		{
			return new ToolPart[] { new RssFeedReaderSettings(), new WebPartToolPart()};
		}

		protected override void CreateChildControls()
		{
            //Set Catalog and Title icons
            this.CatalogIconImageUrl = this.ClassResourcePath + "/feedicon-16.png";
            this.TitleIconImageUrl = this.ClassResourcePath + "/feedicon-16.png";

            refreshButton = new ImageButton();
			refreshButton.ImageUrl = this.ClassResourcePath + "/refresh.gif";
			refreshButton.BorderWidth = Unit.Pixel(0);
            refreshButton.AlternateText = "FeedReader provided by Tim Heuer\nVisit http://timheuer.com/feedreader for more information\n\nClick here to refresh the web part feeds...";
			refreshButton.Click += new ImageClickEventHandler(ForceCacheRefresh);
			this.Controls.Add(refreshButton);
		}

		public void UpdateCache(object sender, System.EventArgs args)
		{
			this.RegisterCommonScript();

			if (this.PartCacheRead(Storage.Shared, "FeedReader" + this.ClientID) == null)
			{
				this.PartCacheWrite(Storage.Shared, "FeedReader" + this.ClientID, this._RefreshFeedCache(), TimeSpan.FromMinutes(Convert.ToDouble(this._cacheDuration)));
			}
			
		}

		private void ForceCacheRefresh(object sender, ImageClickEventArgs args)
		{
			this._ForceCacheRefreshInternal();
		}

		public void ForceCacheRefreshSettings()
		{
			this._ForceCacheRefreshInternal();
		}

		private void _ForceCacheRefreshInternal()
		{
			this.PartCacheInvalidate(Storage.Shared, "FeedReader" + this.ClientID);
		}

		/// <summary>
		/// Render this Web Part to the output parameter specified.
		/// </summary>
		/// <param name="output"> The HTML writer to write out to </param>
		protected override void RenderWebPart(HtmlTextWriter output)
		{
			string outputString = "";
			//output.Write("<table><tr><td><span class=\"ms-descriptiontext\"><b>BETA VERSION NOT FOR PRODUCTION USE</b></span></td></tr></table>");
			output.Write("<br/>");
			
			try
			{
                object res = this.PartCacheRead(Storage.Shared, "FeedReader" + this.ClientID);
                if (res == null)
                    outputString = this._RefreshFeedCache();
                else
                    outputString = res.ToString();
                
                //outputString = this.PartCacheRead(Storage.Shared, "FeedReader" + this.ClientID).ToString();
			}
			catch (Exception ex)
			{
				outputString = "<span class=\"ms-descriptiontext\">An unexpected error occurred: " + ex.Message;
			}
			output.Write(outputString);
			
			// write out the last cache timeframe.
			//output.Write("<table width=\"100%\" border=\"0\" cellpadding=\"1\" cellspacing=\"0\"><tr><td>&nbsp;</td><td colspan=\"2\"><hr/></td><td rowpan=\"2\">&nbsp;</td></tr><tr><td>&nbsp;</td><td width=\"100%\"><span class=\"ms-descriptiontext\">last update on: " + this._lastRefreshTime.ToString(cultureFormat) + "</span></td><td width=\"100%\" align=\"right\">");
			
			this.RenderChildren(output);
			
			output.Write("</td></tr></table>");
		}
		#endregion

		#region Custom Methods
		private string _RefreshFeedCache()
		{
			System.Text.StringBuilder html = new System.Text.StringBuilder();
			bool useProxy = false;

			// get culture specific formatting information for date
			IFormatProvider cultureFormat = (IFormatProvider)System.Threading.Thread.CurrentThread.CurrentCulture.DateTimeFormat.GetFormat(typeof(IFormatProvider));

            html.Append("<style>.sg-item-description{border:1px solid #cccccc;padding:2px;background-color:#FFFFCC}</style>");
            html.Append("<table border=\"0\" cellpadding=\"2\" cellspacing=\"0\">");

			if (this._rssFeeds != null && this._rssFeeds != "")
			{
                // remove trailing slash
                if (_rssFeeds.LastIndexOf(";") == _rssFeeds.Length-1)
                {
                    _rssFeeds = _rssFeeds.Substring(0, _rssFeeds.Length - 1);
                }

				string[] rssFeeds = this._rssFeeds.Replace("; ", ";").Split(";".ToCharArray());

				if (this.ProxyAddress != null && this.ProxyAddress != "")
				{
					useProxy = true;
					_proxyConnection = new System.Net.WebProxy(this._proxyAddress, Convert.ToInt32(this._proxyPort));
					_proxyConnection.Credentials = System.Net.CredentialCache.DefaultCredentials;
				}

				for (int i = 0; i < rssFeeds.Length; i++)
				{
                    string feedTitle = string.Empty;
                    string feedUrl = string.Empty;

                    GenericSyndicationFeed feed = new GenericSyndicationFeed();
                    feed.Load(new Uri(rssFeeds[i].ToString()), System.Net.CredentialCache.DefaultCredentials, _proxyConnection);

					if (rssFeeds[i].ToString().Replace(" ","") != "")
					{
						if (rssFeeds.Length > 1 && i > 0)
							html.Append("<tr><td>&nbsp;</td><td colspan=\"2\" valign=\"top\" height=\"5\">&nbsp;</td></tr>");

						try
						{
                            feedTitle = feed.Title;
							
							// if there is only one feed, change the toolbar title to be the title of the single feed
							if (rssFeeds.Length < 2)
							{
                                this.Title = feed.Title;
							}

                            try
                            {
                                if (feed.Items != null)
                                {
                                    //Determine if the description is being shown and expanded or not
                                    string style;
                                    string plusMinusGif;
                                    if (!this._headLinesOnly && this._expandHeadlines)
                                    {
                                        style = "";
                                        plusMinusGif = "minus.gif";
                                    }
                                    else
                                    {
                                        style = "display:none";
                                        plusMinusGif = "plus.gif";
                                    }

                                    List<SimplifiedFeedItem> simpleFeeds = new List<SimplifiedFeedItem>();

                                    switch (feed.Format)
                                    {
                                        case Argotic.Common.SyndicationContentFormat.Atom:
                                            AtomFeed atom = feed.Resource as AtomFeed;
                                            feedTitle = atom.Title.Content;
                                            foreach (var link in atom.Links)
                                            {
                                                if (link.Relation == "alternate")
                                                {
                                                    feedUrl = link.Uri.AbsoluteUri;
                                                }
                                            }
                                            foreach (var atomItem in atom.Entries)
                                            {
                                                SimplifiedFeedItem aitm = new SimplifiedFeedItem();
                                                aitm.PublishDate = atomItem.PublishedOn;
                                                aitm.Title = atomItem.Title.Content;
                                                aitm.UniqueId = Guid.NewGuid().ToString();
                                                aitm.Content = atomItem.Content.Content;
                                                aitm.Author = atomItem.Authors[0].Name.ToString();
                                                foreach (var link in atomItem.Links)
                                                {
                                                    if (link.Relation == "alternate")
                                                    {
                                                        aitm.Link = link.Uri.AbsoluteUri;
                                                    }
                                                }
                                                aitm.Summary = atomItem.Summary.Content;
                                                simpleFeeds.Add(aitm);
                                            }
                                            break;
                                        case Argotic.Common.SyndicationContentFormat.Rss:
                                            RssFeed rss = feed.Resource as RssFeed;
                                            feedTitle = rss.Channel.Title;
                                            feedUrl = rss.Channel.Link.AbsoluteUri;
                                            foreach (var rssItem in rss.Channel.Items)
                                            {
                                                SimplifiedFeedItem ritm = new SimplifiedFeedItem();
                                                // required elements
                                                ritm.Title = rssItem.Title;
                                                ritm.Content = rssItem.Description;
                                                ritm.Summary = rssItem.Description;
                                                ritm.Link = rssItem.Link.AbsoluteUri;

                                                // optional checks
                                                if (rssItem.PublicationDate != null)
                                                {
                                                    ritm.PublishDate = rssItem.PublicationDate;
                                                }
                                                if (rssItem.Guid != null)
                                                {
                                                    ritm.UniqueId = rssItem.Guid.Value;
                                                }
                                                else
                                                {
                                                    ritm.UniqueId = System.Guid.NewGuid().ToString();
                                                }
                                                if (!string.IsNullOrEmpty(rss.Channel.ManagingEditor))
                                                {
                                                    ritm.Author = rss.Channel.ManagingEditor;
                                                }
                                                else
                                                {
                                                    ritm.Author = "Not specified";
                                                }
                                                
                                                simpleFeeds.Add(ritm);
                                            }
                                            break;
                                        default:
                                            throw new FormatException("You are using an unsupported feed format.  Only RSS and Atom are supported.");
                                            break;
                                    }

                                    html.Append("<tr><td class=\"ms-descriptiontext\">&nbsp;</td><td nowrap colspan=\"2\" valign=\"top\"><span class=\"ms-descriptiontext\"><b><a href=\"" + feedUrl + "\" target=\"_blank\">" + feedTitle + "</a></b></span></td></tr>");

                                    // determine which type of filter mode they are using
                                    if (this._filterMode.ToLower() == "days") // using date filtering
                                    {
                                        foreach (SimplifiedFeedItem itm in simpleFeeds)
                                        {
                                            DateTime compareDate = DateTime.Now.AddDays(-(Convert.ToDouble(this._filterVal)));

                                            if (itm.PublishDate >= compareDate)
                                            {
                                                BuildHtmlForFeedItem(html, i, style, plusMinusGif, itm);
                                            }
                                        }
                                    }
                                    else if (this._filterMode.ToLower() == "items") //using number of items filtering
                                    {
                                        // first need to make sure that the value is not greater than the number of items
                                        int j = simpleFeeds.Count;

                                        if (j > Convert.ToInt32(this._filterVal))
                                            j = Convert.ToInt32(this._filterVal);

                                        for (int k = 0; k < j; k++)
                                        {
                                            SimplifiedFeedItem itm = simpleFeeds[k];
                                            BuildHtmlForFeedItem(html, i, style, plusMinusGif, itm);
                                        }
                                    }
                                    else //none, display the full feed
                                    {
                                        foreach (SimplifiedFeedItem itm in simpleFeeds)
                                        {
                                            BuildHtmlForFeedItem(html, i, style, plusMinusGif, itm);
                                        }
                                    }
                                }
                                else
                                {
                                    html.Append("<tr><td>&nbsp;</td><td colspan=\"2\" valign=\"top\"><span class=\"ms-descriptiontext\">No items exist for this feed at this time.</span></td></tr>");
                                }
                            }
                            catch (Exception ex)
                            {
#if DEBUG
                                html.Append("<tr><td>&nbsp;</td><td colspan=\"2\" valign=\"top\"><span class=\"ms-descriptiontext\">" + ex.ToString() + "</span></td></tr>");
#endif
                                html.Append("<tr><td>&nbsp;</td><td colspan=\"2\" valign=\"top\"><span class=\"ms-descriptiontext\">" + rssFeeds[i].ToString() + " had a problem reading the feeds from the URL.<br>Error returned from FeedReader: " + ex.Message + ":" + ex.Source + "</span></td></tr>");
                            }
						}
						catch(Exception ex)
						{
							if (ex.Message.IndexOf("timed") > 0)
							{
								html.Append("<tr><td>&nbsp;</td><td colspan=\"2\" valign=\"top\"><span class=\"ms-descriptiontext\">");
								html.Append("Searching for your RSS feed has timed out.");
								html.Append("<br>If you use a proxy server, please make sure the address and port are correct in the Web Part settings.");
								html.Append("<br>If you do not use a proxy server, please validate the RSS feed URL is correct.");
								html.Append("</span></td></tr>");
							}
							else
							{
								html.Append("<tr><td>&nbsp;</td><td colspan=\"2\" valign=\"top\"><span class=\"ms-descriptiontext\">" + rssFeeds[i].ToString() + " does not appear to be a valid RSS feed.  If the feed is valid, please ensure your proxy server settings are correct if you use a proxy server.<br>Error returned from FeedReader: " + ex.Message + ":" + ex.Source + "</span></td></tr>");
							}
						}
					}
				}
			}
			else
			{
				html.Append("<tr><td>&nbsp;</td><td colspan=\"2\" valign=\"top\"><span class=\"ms-descriptiontext\">No RSS feeds are specified.  To add feed URLs <a href=\"javascript:MSOTlPn_ShowToolPaneWrapper('1','129','" + this.UniqueID.ToString() + "');\">open the tool pane</a> to add RSS feeds.</span></td></tr>");
			}

            html.Append("</table>");
			html.Append("<table width=\"100%\" border=\"0\" cellpadding=\"1\" cellspacing=\"0\"><tr><td>&nbsp;</td><td colspan=\"2\"><hr/></td><td rowpan=\"2\">&nbsp;</td></tr><tr><td>&nbsp;</td><td width=\"100%\"><span class=\"ms-descriptiontext\">last update on: " + DateTime.Now.ToString(cultureFormat) + "</span></td><td width=\"100%\" align=\"right\">");
			return html.ToString();
		}

        private void BuildHtmlForFeedItem(System.Text.StringBuilder html, int i, string style, string plusMinusGif, SimplifiedFeedItem itm)
        {
            html.Append("<tr>");
            html.Append("<td>&nbsp;</td>");
            // if we are showing the descriptions, then change the image here
            if (!this._headLinesOnly)
            {
                html.Append("<td valign=\"top\">&nbsp;<img alt=\"Click here to expand/collapse item detail\" src=\"" + this.ClassResourcePath + "/" + plusMinusGif + "\" border=\"0\" onclick=\"toggleRssItem(this,'" + this.ClassResourcePath + "');\" id=\"" + this.ID + "_FEED" + i.ToString() + "_ITEM" + itm.UniqueId.ToString() + "\"></td>");
            }
            else
            {
                html.Append("<td valign=\"top\">&nbsp;<img alt=\"Outline bulletpoint icon\" src=\"" + this.ClassResourcePath + "/square.gif\" border=\"0\"></td>");
            }

            html.Append("<td valign=\"top\" width=\"100%\"><span class=\"ms-descriptiontext\">");
            if (this._showDates)
                html.Append("(" + itm.PublishDate.ToShortDateString() + ") ");
            html.Append("<a target=\"_blank\" href=\"" + itm.Link + "\" title=\"" + itm.Title + "\n" + itm.Link + "\n\nby: " + itm.Author + "\">" + itm.Title + "</a>");
            if (!this._headLinesOnly)
            {
                html.Append("<br><div id=\"" + this.ID + "_FEED" + i.ToString() + "_ITEM" + itm.UniqueId.ToString() + "__child" + "\" style=\"" + style + "\" class=\"sg-item-description\">" + itm.Content + "</div>");
            }
            html.Append("</span></td>");
            html.Append("</tr>");
        }
		#endregion

		#region IDesignTimeHtmlProvider Members
		public string GetDesignTimeHtml()
		{
            return "<center><img alt=\"Tim Heuer logo\" src=\"" + this.ClassResourcePath + "/thlogo-32.png\" border=\"0\"><p/>FeedReader provided by <a href=\"http://timheuer.com/feedreader\" target=\"_blank\">Tim Heuer</a></center>";
		}

		#endregion
	}
}